package framework.common;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

abstract public class HubServer implements Runnable {

	// ユーザが所有するデバイス名の集合
	// String user -> Set<String deviceName>
	private Map<String, Set<String>> ownDeviceNames;

	// デバイスの所有者名
	// String deviceName -> String user
	private Map<String, String> owners;

	// 内部応答キュー
	// String deviceName -> List<DeviceRequest>
	private Map<String, List<DeviceRequest>> deviceRequests;

	// 外部応答キュー
	// Client -> List<DeviceRequest>
	private Map<Client, List<DeviceResponse>> deviceResponses;

	// String deviceName -> DeviceServer
	protected Map<String, DeviceServer> devices;

	/**
	 * ハブサーバを作成する。
	 */
	public HubServer() {
		ownDeviceNames = new HashMap<String, Set<String>>();
		owners = new HashMap<String, String>();
		deviceRequests = new HashMap<String, List<DeviceRequest>>();
		deviceResponses = new HashMap<Client, List<DeviceResponse>>();

		devices = new HashMap<String, DeviceServer>();
	}

	/**
	 * デバイスdevをユーザuserの所有物として登録する
	 *
	 * @param user
	 *            ユーザ名
	 * @param dev
	 *            デバイス名
	 */
	public void register(String user, DeviceServer dev) {
		String deviceName = dev.getDeviceName();
		// ユーザの所有物一覧に登録する。
		Set<String> ownDevices = null;
		if (!ownDeviceNames.containsKey(user)) {
			ownDevices = new HashSet<String>();
			ownDeviceNames.put(user, ownDevices);
		}
		ownDevices = ownDeviceNames.get(user);
		ownDevices.add(deviceName);
		// デバイスの所有者を登録する。
		owners.put(deviceName, user);
		// デバイスの内部要求キューを作成する。
		if (!deviceRequests.containsKey(deviceName)) {
			deviceRequests.put(deviceName, new LinkedList<DeviceRequest>());
		}
		// デバイスを登録する。
		devices.put(deviceName, dev);
	}

	/**
	 * DeviceServerから応答を受けとる
	 *
	 * @param res
	 *            デバイスの応答
	 */
	public void putResponse(DeviceResponse res) {
		System.out.println("put response " + res);
		Client c = res.getRequest().getClient();
		if (deviceResponses.containsKey(c)) {
			deviceResponses.get(c).add(res);
		} else {
			throw new RuntimeException("no such client");
		}
	}

	public boolean hasRequest(DeviceServer deviceServer) {
		return deviceRequests.get(deviceServer.getDeviceName()).size() > 0;
	}

	/**
	 * deviceServerに要求を返す。<br/>
	 * 内部応答キューの先頭を取得および削除します。キューが空の場合は null を返します。
	 *
	 * @param deviceServer
	 *            デバイスサーバ
	 * @return デバイス要求メッセージ
	 */
	public DeviceRequest pollRequest(DeviceServer deviceServer) {
		int b = deviceRequests.get(deviceServer.getDeviceName()).size();
		DeviceRequest req = deviceRequests.get(deviceServer.getDeviceName())
				.remove(0);
		int a = deviceRequests.get(deviceServer.getDeviceName()).size();
		System.out.println(b + "->" + a);
		return req;
	}

	/**
	 * deviceServerに要求を返す。<br/>
	 *
	 * @param deviceServer
	 * @return デバイス要求メッセージ
	 */
	public DeviceRequest getRequest(DeviceServer deviceServer) {
		return pollRequest(deviceServer);
	}

	public boolean hasResponse(Client client) {
		return deviceResponses.get(client).size() > 0;
	}

	/**
	 * 指定したクライアントに対する応答を返す。
	 *
	 * @param client
	 *            応答を返すクライアント
	 * @return 応答
	 */
	public DeviceResponse getResponse(Client client) {
		int b = deviceResponses.get(client).size();
		DeviceResponse res = deviceResponses.get(client).remove(0);
		int a = deviceResponses.get(client).size();
		System.out.println(b + "->" + a);
		return res;
	}

	/**
	 * Clientから要求を受け取る.
	 *
	 * @param req
	 *            要求
	 */
	public void putRequest(DeviceRequest req) {
		System.out.println("put request " + req);
		// 外部応答キューを作成する。
		Client client = req.getClient();
		if (!deviceResponses.containsKey(client)) {
			List<DeviceResponse> queue = new LinkedList<DeviceResponse>();
			deviceResponses.put(client, queue);
		}
		// 要求をデバイスの内部要求キューへ振り分ける
		String user = client.getUser();
		String deviceName = client.getDeviceName();
		if (hasDevice(user, deviceName)) {
			deviceRequests.get(deviceName).add(req);
			// System.out.println("put request");
		} else {
			throw new RuntimeException("no such device " + deviceName);
		}
	}

	/**
	 * ユーザがデバイスを所有しているかどうか
	 *
	 * @param user
	 * @param deviceName
	 * @return 所有者なら真
	 */
	public boolean hasDevice(String user, String deviceName) {
		if (!ownDeviceNames.containsKey(user))
			return false;
		return ownDeviceNames.get(user).contains(deviceName);
	}

	public void start() {

	}

	public void stop() {

	}

}
